Btrfs 相关
Table of Contents
1. 基础知识
1.1. 概念解释
- extent
它是用于构成 chunk 的,由数个连续 block 组成的逻辑空间。它的语义通常是「数个 block 组成的连续的逻辑空间」。
extent 是存储数据的最小的逻辑单位 (存储数据的最小的物理单位是 block),只能作为整体被不同对象共享 (比如 reflink),不能存储不同对象的数据 (比如一个 extent 不能存储两个文件的数据)。extent 的大小由 btrfs 决定,最小为 4K,一旦确定就不可被更改。
比如,有一个 1M 大小的文件使用了 1 个 1M 大小的 extent,那么再向这个文件写入 512K 的新数据之后,这个文件最终会使用 1M 大小的 extent 和 512K 大小的 extent 一共两个 extent。只有当 1M 大小的 extent 中的数据被写往别处时,这个旧的 1M 大小的 extent 才被释放。如果修改了 1M extent 中的一部分数据,可能导致这个 1M extent 分裂成三份 (修改位置之前的、修改位置的和修改位置之后的数据);或者 btrfs 读出整个 extent 的数据,修改完后将这一整个 extent 覆写会原位置。总之,extent 的管理策略极为复杂,用户不需要关心。
如果某个文件被 btrfs 压缩后存储在硬盘上,那么该文件被压缩的 extent 的最大大小是 128K。这是因为 btrfs 在读取被压缩的数据中的某个 block 时,必须先解压完整的 extent,设置过大的 extent 会严重影响被压缩的文件的读写性能。为了避免这个问题,btrfs 取了一个折中的 128K 大小作为被压缩的数据的最大的 extent 的大小。
- chunk (block group)
btrfs 中有三种 chunk: data, metadata 和 system。chunk 是由数个 extent 组成的一个逻辑空间。chunk 和 block group 有时可以互换使用。它的语义通常是「被 btrfs 动态分配,用于存储 data, metadata 或 system 数据的空间」。extent 最终会被写入到 chunk 中。
这三类 chunk 的大小只能在创建文件系统时修改。通常,data chunk 的大小总是 1G。在文件系统小于 50G 时,metadata chunk 大小是 256M;在文件系统大小为 50G 及以上时,metadata chunk 大小是 1G。文档里只说 system chunk 的大小是数 M,经测试,在 4*8T raid10 的文件系统中,system chunk 大小是 32M。
1.2. 文件自动修复 (self healing)
btrfs 与 zfs 类似,也支持异常数据自动修复。与 zfs 相同,btrfs 文件系统中,只有在数据有副本,比如使用 dup, raid10, raid1c3 等存储模式;或者使用 raid5/6 这种可以恢复文件的存储模式时,btrfs 才会自动修复异常的数据。
如果数据没有副本,或者没有使用可以修复数据的存储模式,则 btrfs 与 zfs 相同,只能检测到数据异常,但无法修复。
2. 使用技巧
2.1. 挂载参数
btrfs 的挂载参数按影响范围可以划分为针对全局的参数和针对子卷 subvolume 的参数。
btrfs 的全局挂载参数由首个被挂载的子卷决定,如果有些必须的挂载参数没有被用户指定,那就使用默认值。并且,后续挂载其他子卷时使用的全局参数全部不生效。
此外,需要注意:
- 不能单独为某个子卷关闭 CoW 特性,因为 nodatacow 是个全局挂载参数,但可以通过
chattr +C <path>
命令对某个文件或目录关闭 CoW 特性 (作用于目录时,目录内已有文件不受影响,新文件关闭 CoW 特性)
2.1.1. compress 和 compress-force
compress 挂载选项表示,btrfs 会压缩要被压缩的文件的前一部分数据 (排在前面的数个 extent),如果文件的前一部分数据压缩效果好,那么这个文件中,每一个 extent 都会被 btrfs 压缩一遍;如果文件的前一部分数据压缩效果不好,那么整个文件都不会被压缩,直接存入硬盘。
compress-force 挂载选项表示,btrfs 不会判断文件是否可压缩,而是直接压缩文件的每一个 extent。
这些被压缩的 extent 中,压缩效果好才会被保留,压缩效果不好的 extent 会丢弃被压缩好的数据,直接使用压缩前的数据。
注: 我没找见这个衡量压缩效果好坏的量化指标。
就个人使用情况与经验而言,使用 compress 即可,不推荐使用 compress-force 作为挂载选项。特别是存储大量媒体数据的使用场景。
不推荐的理由是: compress-force 会导致不可压缩文件的碎片化程度急剧增加。
给一个例子,这是一个视频文件分别被 compress-force 和 compress 挂载参数处理后的效果:
Processed 1 file, 14282 regular extents (14282 refs), 0 inline. Type Perc Disk Usage Uncompressed Referenced TOTAL 99% 6.9G 6.9G 6.9G none 100% 6.9G 6.9G 6.9G zstd 35% 5.9M 16M 16M Processed 1 file, 311 regular extents (311 refs), 0 inline. Type Perc Disk Usage Uncompressed Referenced TOTAL 100% 6.9G 6.9G 6.9G none 100% 6.9G 6.9G 6.9G
从例子中可以看到,压缩视频文件只能节省约 10M 的空间,但是文件碎片化程度有了指数级的上升。
这是因为,前面提到,被压缩后的 extent 的最大大小是 128K,而 compress-force 选项会导致所有的数据都被压缩一遍,这就意味着原本可以使用较大 extent 的文件必须被更加地细分,将数据放入小的 extent 中,才能满足压缩算法的需要。
而这种行为恰恰导致了文件的碎片化。特别是对于媒体文件来说,文件越大,碎片化越严重。
如果想要把受 compress-force 压缩模式影响的数据重新使用 compress 压缩模式压缩一遍,以减小文件的碎片化程度,可以使用通过 defragment 来变更压缩算法的方法来让所有文件以 compress 压缩模式被重新压缩。
2.1.2. datacow/nodatacow 和 datasum/nodatasum
datacow 和 nodatacow 的含义为是否为该挂载点内的数据启用 CoW 特性。
datasum 和 nodatasum 的含义为是否计算并存储该挂载点内的 extent 的 checksum。
nodatacow 和 nodatasum 启用后,都禁用了文件系统压缩功能。
提这两类挂载选项是因为: datasum 与 datacow 互相隐含;nodatasum 与 nodatacow 互相隐含。
也就是说,如果不启用 CoW 特性,那么 extent 的 checksum 也不会被计算并存储,反之亦然;如果启用 CoW 特性,那么 extent 的 checksum 会被计算并存储,反之亦然。
2.2. 变更压缩算法 (通过 defragment)
修改子卷使用的压缩算法之后,可以使用 btrfs filesystem defragment -r -v -c<compress-alg> <mount-point>
开始对子卷的挂载点进行碎片整理, -c
参数会让所有文件在碎片整理过程中被新的压缩算法重新压缩。
注: -r
递归目录,但不会递归地进入挂载点或子卷; -v
输出详细过程; -c
指定压缩算法。
但是需要注意,在对建有快照的子卷上进行碎片整理可能会触发 CoW 导致没有变更的 extent 也被重新分配了空间,而由于快照的存在,旧 extent 占有的空间也不会被释放,从而导致空间使用率增加。
2.3. 碎片整理的 defragment 操作
使用 CoW 特性的文件系统会比非 CoW 文件系统更容易产生碎片。
因为,在使用 CoW 特性时,当一个数据被从硬盘上读出,并写回硬盘,CoW 文件系统的每一次都会将这些数据写入到硬盘上的另一个位置,而不是数据原先在硬盘上所处的位置。
所以,使用一段时间后,extent 可能被分配得过于离散,或 extent 被打散到较小的空间中,这都会增加硬盘寻道消耗的时间。
碎片整理 btrfs filesystem defragment [options] <file>|<dir> [<file>|<dir>…]
可以将被碎片化存储 (分布在多个、多处的 extent 中) 的数据,重新读到内存中,然后尽可能地将他们一起写入到硬盘上。
defragment 优化的是 extent 的分布和分配,它将被打散的 extent 重新组合成一个较大的 extent,并使文件的所有 extent 在硬盘上的位置尽可能地近。所以 defragment 会让数据所在的位置更加连续,从而减少硬盘寻道消耗的时间。
这个命令有一个重要的参数 -t
(默认值 32M),它向碎片整理操作传递了一个描述 extent 大小的值。表示,大小低于该值的 extent 会被碎片整理程序考虑与该文件的其他 extent 合并并重新写入。多数时候,保持默认即可。
碎片整理之后,可用空间有可能不连续,此时可以进一步用 balance 重新排列数据,从而让可用空间尽量连续。
2.4. 让数据均匀分布的 balance 操作
btrfs balance 是一种获取 btrfs 文件系统上的所有数据和元数据,然后通过分配器算法进行传递,最后将其重新写入磁盘上的不同位置的操作。它最初为多设备文件系统设计,目的是让数据在在设备间更均匀地分布。所以这个操作在将新设备添加到几乎已满的文件系统时尤其有用。
balance 优化的是 chunk 的分布和分配,它将未被充分使用的 chunk 中的数据合并在有空余空间的 chunk 中,并回收腾出的 chunk,并让 chunk 的分布尽量连续。所以在 chunk 数量不足时,它可以回收一些未被充分使用的 chunk,从而让 btrfs 拥有更多可分配的空间;也可以在 chunk 被分配得过于离散时,重新调整 chunk 的分布,使其更加连续以减少硬盘寻道消耗的时间。
这个功能还有一些其它作用:
- 可以用于重建 raid。在一个使用了 raid1 的 btrfs 上,如果有一个新硬盘替换了已经损坏的硬盘,那么在这个新硬盘被加入文件系统之后,新硬盘中没有数据。balance 操作会让 btrfs 重建应该存在于新硬盘上的数据
- 可以改变 data 或者 metadata 的存储模式,比如让将文件 data 部分在硬盘上存储两份
btrfs balance start -dconvert=dup <path>
(metadata 的默认模式就是 dup ,即默认在硬盘上存两份相同的 metadata)
在 btrfs 3.14 之后,有时需要使用 balance 操作来修复由于 chunk 用尽导致无法给 metadata 分配 chunk 从而出现文件系统已满的问题。这里要注意只 balance data chunk,不要动 metadata chunk,因为元数据越是集中存放,将来就越可能需要分配新的 chunk,就越有可能遇到没 chunk 可以分配给 data 的情况。
有一个指标可以用来参考是否需要 balance:
# btrfs filesystem df /mnt/mp_storage0 Data, RAID10: total=4.38TiB, used=4.37TiB # total 指已分配的空间,used 指当前用了多少已分配的空间 System, RAID10: total=16.00MiB, used=464.00KiB Metadata, RAID10: total=7.50GiB, used=7.01GiB GlobalReserve, single: total=512.00MiB, used=0.00B
used/total 如果得出的百分比过低,则有可能出现 chunk 中有大量未使用空间的情况。而这种情况下 defragment 操作没有用,defragment 只影响文件的连续性,不影响 chunk 分配。所以此时最好使用 balance 把未使用的 chunk 空间回收一下。
3. 实用工具 (第三方)
- btrfs-compsize
btrfs 官方没有提供查看压缩率情况的工具,这个工具可以查看 btrfs 子卷挂载点的压缩情况。
- btrfs-heatmap
可以查看 block group 的分布和使用情况。
- snapper
由 SUSE 开发,可以实现快照的自动管理。可配置参数丰富,以命令行为主要使用方式。